import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" import { createEffect, For, Show } from "solid-js" import { withActor } from "~/context/auth.withActor" import { createStore } from "solid-js/store" import styles from "./member-section.module.css" import { UserRole } from "@opencode-ai/console-core/schema/user.sql.js" import { Actor } from "@opencode-ai/console-core/actor.js" import { User } from "@opencode-ai/console-core/user.js" import { RoleDropdown } from "./role-dropdown" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { formError, localizeError } from "~/lib/form-error" const listMembers = query(async (workspaceID: string) => { "use server" return withActor(async () => { return { members: await User.list(), actorID: Actor.userID(), actorRole: Actor.userRole(), } }, workspaceID) }, "member.list") const inviteMember = action(async (form: FormData) => { "use server" const email = (form.get("email") as string | null)?.trim() if (!email) return { error: formError.emailRequired } const workspaceID = form.get("workspaceID") as string | null if (!workspaceID) return { error: formError.workspaceRequired } const role = form.get("role") as (typeof UserRole)[number] | null if (!role) return { error: formError.roleRequired } const limit = form.get("limit") as string | null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } return json( await withActor( () => User.invite({ email, role, monthlyLimit }) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string })), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.create") const removeMember = action(async (form: FormData) => { "use server" const id = form.get("id") as string | null if (!id) return { error: formError.idRequired } const workspaceID = form.get("workspaceID") as string | null if (!workspaceID) return { error: formError.workspaceRequired } return json( await withActor( () => User.remove(id) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string })), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.remove") const updateMember = action(async (form: FormData) => { "use server" const id = form.get("id") as string | null if (!id) return { error: formError.idRequired } const workspaceID = form.get("workspaceID") as string | null if (!workspaceID) return { error: formError.workspaceRequired } const role = form.get("role") as (typeof UserRole)[number] | null if (!role) return { error: formError.roleRequired } const limit = form.get("limit") as string | null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } return json( await withActor( () => User.update({ id, role, monthlyLimit }) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string })), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.update") function MemberRow(props: { member: any workspaceID: string actorID: string actorRole: string roleOptions: { value: string; label: string; description: string }[] }) { const i18n = useI18n() const submission = useSubmission(updateMember) const isCurrentUser = () => props.actorID === props.member.id const isAdmin = () => props.actorRole === "admin" const [store, setStore] = createStore({ editing: false, selectedRole: props.member.role as (typeof UserRole)[number], limit: "", }) createEffect(() => { if (!submission.pending && submission.result && !submission.result.error) { setStore("editing", false) } }) function show() { while (true) { submission.clear() if (!submission.result) break } setStore("editing", true) setStore("selectedRole", props.member.role) setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "") } function hide() { setStore("editing", false) } function getUsageDisplay() { const currentUsage = (() => { const dateLastUsed = props.member.timeMonthlyUsageUpdated if (!dateLastUsed) return 0 const current = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", timeZone: "UTC", }) const lastUsed = dateLastUsed.toLocaleDateString(undefined, { year: "numeric", month: "long", timeZone: "UTC", }) return current === lastUsed ? (props.member.monthlyUsage ?? 0) : 0 })() const limit = props.member.monthlyLimit ? `$${props.member.monthlyLimit}` : i18n.t("workspace.members.noLimitLowercase") return `$${(currentUsage / 100000000).toFixed(2)} / ${limit}` } const roleLabel = (value: string) => props.roleOptions.find((option) => option.value === value)?.label ?? value return (
{i18n.t("workspace.members.subtitle")}
| {i18n.t("workspace.members.table.email")} | {i18n.t("workspace.members.table.role")} | {i18n.t("workspace.members.table.monthLimit")} |
|---|